classdef ELM_model < handle
    % ELM_MODEL 极限学习机（Extreme Learning Machine）模型实现
    %
    % 此类实现了ELM模型的训练和测试功能，支持回归和分类任务。
    % 特性：
    %   - 支持多种激活函数（sigmoid, sine, hardlim, tribas, radbas）
    %   - 支持正则化权重调整
    %   - 自动处理分类任务的标签编码
    %   - 计算训练/测试时间和准确率
    %
    % 示例：
    %   model = ELM_model(0, 100, 'sig', 1e5);  % 创建回归模型
    %   model.train(train_data);                % 训练模型
    %   model.test(test_data);                  % 测试模型
    %   disp(model.TrainingAccuracy);           % 输出训练准确率
    
    properties
        Task_Type             % 任务类型：0=回归(Regression)，1=分类(Classification)
        NumberofHiddenNeurons % 隐含层神经元数量
        ActivationFunction    % 激活函数类型：'sig','sin','hardlim','tribas','radbas'
        weight                % 正则化权重（用于防止过拟合）
        Solver_flag           % 求解标志位
    end
    
    properties (Access = private)
        InputWeight           % 输入层到隐含层的权重矩阵 [hidden_neurons x input_dim]
        BiasofHiddenNeurons   % 隐含层神经元的偏置向量 [hidden_neurons x 1]
        OutputWeight          % 隐含层到输出层的权重矩阵 [output_dim x hidden_neurons]
        label                 % 训练集的唯一标签（仅分类任务使用）
        train_time_           % 训练耗时（秒）私有存储
        test_time_            % 测试耗时（秒）私有存储
        train_accuracy_       % 训练精度（RMSE回归/准确率分类）私有存储
        test_accuracy_        % 测试精度（RMSE回归/准确率分类）私有存储
    end
    
    properties (Dependent)
        TrainingTime          % 获取训练耗时（秒）
        TestingTime           % 获取测试耗时（秒）
        TrainingAccuracy      % 获取训练精度
        TestingAccuracy       % 获取测试精度
    end
    
    methods
        function obj = ELM_model(Task_Type, NumberofHiddenNeurons, ActivationFunction, weight,Solver_flag)
            % ELM_MODEL 构造函数初始化ELM模型参数
            %
            % 输入：
            %   Task_Type             - 任务类型 (0:回归, 1:分类)
            %   NumberofHiddenNeurons - 隐含层神经元数量
            %   ActivationFunction    - 激活函数名称（字符串）
            %   weight                - 正则化权重（标量）
            obj.Task_Type = Task_Type;
            obj.NumberofHiddenNeurons = NumberofHiddenNeurons;
            obj.ActivationFunction = ActivationFunction;
            obj.weight = weight;
            obj.Solver_flag = Solver_flag;
        end
        
        function train(obj, train_data)
            % TRAIN 训练ELM模型
            %
            % 输入：
            %   train_data - 训练数据矩阵 [样本数 x (标签+特征)]
            %               第1列为标签，其余列为特征
            
            REGRESSION = 0;  % 回归任务标识
            
            % 分离标签和特征
            T = train_data(:,1)';              % 标签行向量 [1 x samples]
            P = train_data(:,2:end)';           % 特征矩阵 [features x samples]
            
            % 分类任务：预处理标签（one-hot编码并转换为-1/1格式）
            if obj.Task_Type ~= REGRESSION
                [T, obj.label] = obj.preprocess_labels(T);
            end
            
            % 随机初始化输入权重和偏置（范围：[-1, 1]）
            obj.InputWeight = rand(obj.NumberofHiddenNeurons, size(P,1)) * 2 - 1;
            obj.BiasofHiddenNeurons = rand(obj.NumberofHiddenNeurons, 1);
            
            % 计算隐含层输出矩阵 H
            start_time = cputime;  % 开始计时
            tempH = obj.InputWeight * P;  % 加权输入
            BiasMatrix = repmat(obj.BiasofHiddenNeurons, 1, size(P,2));  % 扩展偏置矩阵
            tempH = tempH + BiasMatrix;      % 加入偏置
            H = obj.activate(tempH, obj.ActivationFunction);  % 应用激活函数
            clear P tempH;  % 清理中间变量释放内存
            
            % 计算输出权重（Moore-Penrose伪逆或正则化求解）
            if obj.weight == 10^100  % 无正则化
                if obj.Solver_flag == 0
                    obj.OutputWeight = pinv(H') * T';
                else
                    obj.OutputWeight = QRIL_inverse.QR_schmidt(H',T'); % H'OutputWeight = T'
                end
            else  % 带正则化求解

                if obj.Solver_flag == 0
                    obj.OutputWeight = (eye(size(H,1)) / obj.weight + H * H') \ (H * T');
                else
                    obj.OutputWeight = QRIL_inverse.QR_schmidt((eye(size(H,1)) / obj.weight + H * H'),eye(size(H,1)))*H * T';
                end   
                
            end
            
            % 记录训练结果
            obj.train_time_ = cputime - start_time;  % 计算训练时间
            Y = (H' * obj.OutputWeight)';            % 训练集预测输出
            obj.train_accuracy_ = obj.calculate_accuracy(T, Y, obj.Task_Type);  % 计算精度
        end
        
        function test(obj, test_data)
            % TEST 测试ELM模型性能
            %
            % 输入：
            %   test_data - 测试数据矩阵 [样本数 x (标签+特征)]
            
            REGRESSION = 0;  % 回归任务标识
            
            % 分离标签和特征
            TV_T = test_data(:,1)';            % 测试标签 [1 x samples]
            TV_P = test_data(:,2:end)';         % 测试特征 [features x samples]
            
            % 分类任务：使用训练集的标签映射进行编码
            if obj.Task_Type ~= REGRESSION
                TV_T = obj.encode_labels(TV_T);
            end
            
            % 计算测试集的隐含层输出
            start_time = cputime;
            tempH_test = obj.InputWeight * TV_P;
            BiasMatrix = repmat(obj.BiasofHiddenNeurons, 1, size(TV_P,2));
            tempH_test = tempH_test + BiasMatrix;
            H_test = obj.activate(tempH_test, obj.ActivationFunction);
            clear TV_P tempH_test;
            
            % 计算预测输出
            TY = (H_test' * obj.OutputWeight)';  % 测试集预测输出 [output_dim x samples]
            
            % 记录测试结果
            obj.test_time_ = cputime - start_time;
            obj.test_accuracy_ = obj.calculate_accuracy(TV_T, TY, obj.Task_Type);
        end
        
        %% 依赖属性的访问方法
        function value = get.TrainingTime(obj)
            value = obj.train_time_;  % 返回训练时间
        end
        
        function value = get.TestingTime(obj)
            value = obj.test_time_;   % 返回测试时间
        end
        
        function value = get.TrainingAccuracy(obj)
            value = obj.train_accuracy_;  % 返回训练精度
        end
        
        function value = get.TestingAccuracy(obj)
            value = obj.test_accuracy_;   % 返回测试精度
        end
    end
    
    methods (Access = private)
        function H = activate(~, tempH, ActivationFunction)
            % ACTIVATE 应用指定的激活函数
            %
            % 输入：
            %   tempH              - 加权输入矩阵
            %   ActivationFunction - 激活函数名称
            % 输出：
            %   H - 激活后的矩阵
            
            switch lower(ActivationFunction)
                case {'sig','sigmoid'}  % Sigmoid函数
                    H = 1 ./ (1 + exp(-tempH));
                case {'sin','sine'}     % Sine函数
                    H = sin(tempH);
                case 'hardlim'          % 硬限幅函数
                    H = double(tempH >= 0);
                case 'tribas'           % 三角基函数: f(x)=max(1-|x|,0)
                    H = max(1 - abs(tempH), 0);
                case 'radbas'           % 径向基函数: f(x)=exp(-x²)
                    H = exp(-(tempH.^2));
                otherwise
                    error('不支持的激活函数: %s', ActivationFunction);
            end
        end
        
        function accuracy = calculate_accuracy(~, T, Y, Task_Type)
            % CALCULATE_ACCURACY 计算模型预测精度
            %
            % 回归任务：返回均方根误差（RMSE）
            % 分类任务：返回分类准确率
            %
            % 输入：
            %   T          - 真实标签
            %   Y          - 预测输出
            %   Task_Type  - 任务类型标识
            
            if Task_Type == 0  % 回归任务
                accuracy = sqrt(mean((T(:) - Y(:)).^2));  % 计算RMSE
            else  % 分类任务
                miss = 0;  % 错误计数
                for i = 1:size(T, 2)
                    [~, expected] = max(T(:,i));  % 真实类别索引
                    [~, actual] = max(Y(:,i));    % 预测类别索引
                    if actual ~= expected
                        miss = miss + 1;
                    end
                end
                accuracy = 1 - miss / size(T, 2);  % 计算准确率
            end
        end
        
        function [encoded_labels, unique_labels] = preprocess_labels(obj, raw_labels)
            % PREPROCESS_LABELS 预处理分类标签（训练阶段）
            %
            % 步骤：
            %   1. 获取唯一标签
            %   2. 创建one-hot编码矩阵
            %   3. 将{0,1}编码转换为{-1,1}（ELM标准格式）
            %
            % 输入：
            %   raw_labels - 原始标签向量
            % 输出：
            %   encoded_labels - 编码后的标签矩阵 [num_classes x samples]
            %   unique_labels  - 唯一标签列表
            
            unique_labels = unique(raw_labels);  % 获取唯一标签
            num_classes = length(unique_labels);
            
            % 初始化one-hot编码矩阵
            encoded_labels = zeros(num_classes, length(raw_labels));
            for i = 1:length(raw_labels)
                class_idx = find(unique_labels == raw_labels(i));  % 查找类别索引
                encoded_labels(class_idx, i) = 1;
            end
            
            % 转换为ELM标准格式：-1/1
            encoded_labels = encoded_labels * 2 - 1;
        end
        
        function encoded_labels = encode_labels(obj, raw_labels)
            % ENCODE_LABELS 使用训练集的标签映射编码测试标签
            %
            % 注意：此方法依赖训练阶段生成的obj.label
            %
            % 输入：
            %   raw_labels - 测试集原始标签向量
            % 输出：
            %   encoded_labels - 编码后的标签矩阵
            
            num_samples = length(raw_labels);
            encoded_labels = zeros(length(obj.label), num_samples);
            
            for i = 1:num_samples
                class_idx = find(obj.label == raw_labels(i));  % 查找训练标签索引
                if isempty(class_idx)
                    error('测试标签 %d 未在训练集中出现', raw_labels(i));
                end
                encoded_labels(class_idx, i) = 1;
            end
            
            % 转换为ELM标准格式：-1/1
            encoded_labels = encoded_labels * 2 - 1;
        end
    end
end